using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Hosting;

namespace OpenWaves.Web
{
    public class AssemblyResourceVirtualFile : VirtualFile
    {
        private readonly string appRelativePath;
        private readonly string assemblyName;
        private readonly string resourceName;

        public AssemblyResourceVirtualFile(string virtualPath)
            : base(virtualPath)
        {
            this.appRelativePath = VirtualPathUtility.ToAppRelative(virtualPath);

            var parts = this.appRelativePath.Split('/');
            if (parts.Length < 4)
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Expected format: ~/App_Resource/MyAssemblyName/MyTypeName.ascx but was {0}.", virtualPath),
                    virtualPath);
            }

            this.assemblyName = parts[2];
            this.resourceName = parts[3];
        }

        protected string AppRelativePath
        {
            get { return this.appRelativePath; }
        }
        protected string AssemblyName
        {
            get { return this.assemblyName; }
        }
        protected string ResourceName
        {
            get { return this.resourceName; }
        }

        public override Stream Open()
        {
            //read into memory to avoid locking file
            Assembly assembly = Assembly.Load(this.assemblyName);

            Stream stream = GetResourceStream(assembly);

            ThrowIfNotPublicResource(assembly, this.resourceName);

            return stream;
        }

        protected virtual Stream GetResourceStream(Assembly assembly)
        {
            Stream stream = assembly.GetManifestResourceStream(resourceName);
            if (stream == null)
            {
                var availableResources =
                    assembly.GetManifestResourceNames().OrderBy(name => name).Select(name => string.Format(CultureInfo.InvariantCulture, "'{0}'", name));
                var message = String.Format(CultureInfo.InvariantCulture, "Cannot find resource '{0}' in assembly '{1}'. Available resources: {2}", resourceName,
                    assembly, availableResources.JoinStrings(", "));

                throw new InvalidOperationException(message);
            }
            return stream;
        }

        protected static void ThrowIfNotPublicResource(Assembly assembly, string resourceName)
        {
            var publicResourceNames = AssemblyResourceVirtualPathProvider.GetPublicResourceNames(assembly);

            var distinctPublicResources = publicResourceNames.Distinct().ToList();
            bool isPublicResource = distinctPublicResources.Any(r => r == resourceName);

            if (isPublicResource == false)
            {
                var publicResourcesText =
                    distinctPublicResources.OrderBy(name => name).Select(name => string.Format(CultureInfo.InvariantCulture, "'{0}'", name)).JoinStrings(", ");
                var message = String.Format(CultureInfo.InvariantCulture,
                    "Resource '{0}' is not marked as public. Use [assembly:{1}(\"{0}\")] to mark resource as public. Public resources: {2}.", resourceName,
                    typeof(PublicAssemblyResourcesAttribute), publicResourcesText);
                throw new InvalidOperationException(message);
            }
        }
    }
}